#include "file.h" // Класс для чтения паролей из файла и запуска подбора
#include "log.h"  // Введение логов
#include "nuclei.h" //Сканировани nuclei
#include <args.hxx> //парсинг флагов из командной строки
#include <chrono> // Определение времени работы программы
#include <cmath>   // Для round()
#include <iomanip> // Для std::setprecision
#include <stdexcept>

int main(int argc, char **argv) {
  // Конфигурация флагов для терминала
  args::ArgumentParser p("╭━━╮╱╱╱╭╮╱╱╭━━┳━━┳╮╭╮\n┃╭╮┣┳┳┳┫╰┳━┫━━┫━━┫╰╯┃\n┃╭╮┃╭┫┃"
                         "┃╭┫┻╋━━┣━━┃╭╮┃\n╰━━┻╯╰━┻━┻━┻━━┻━━┻╯╰╯");
  args::Group arguments(p, "arguments", args::Group::Validators::DontCare,
                        args::Options::Global);
  args::ValueFlag<std::string> target(
      arguments, "target", "The ip addres brutforce", {'t', "target"});
  args::ValueFlag<std::string> username(
      arguments, "username", "The username brutforce", {'u', "user"});
  args::ValueFlag<std::string> file_name_password(
      arguments, "file_password", "The file to be processed passwords", {'f'});
  args::ValueFlag<std::string> file_name_password_user(
      arguments, "file_password_user",
      "The file to be processed passwords and user", {'F'});
  args::ValueFlag<std::string> file_name_output(arguments, "file_output",
                                                "The file to be processed log",
                                                {'o', "output"});
  args::Flag scan_ssh(arguments, "scan_ssh",
                      "Detected ssh server(nuclei), default=false",
                      {'s', "scan_ssh"}, false);
  args::Flag scan_honeypot(
      arguments, "scan_honeypot",
      "Servers detected as honeypots(nuclei), default=false",
      {'H', "honeypots"}, false);
  args::ValueFlag<int> threads_max(
      arguments, "threads",
      "Max threads to scan, default=" +
          std::to_string(std::thread::hardware_concurrency()),
      {'T', "threads"}, std::thread::hardware_concurrency());

  args::ValueFlag<unsigned int> port(
      arguments, "port", "Port server, default=22", {'p', "port"}, 22);
  args::Flag version(arguments, "version", "Print version programm",
                     {'v', "version"});

  args::HelpFlag h(arguments, "help", "Print help message", {'h', "help"});

  /* Создайём отдельный catch для того, чтобы правильно инициализировать класс
    logging и он был доступен в exception catch
    Обычно инициализация класса "p.ParseCLI(argc, argv);" должно быть в catch
    т.к класс кидает ошибки help, Error. Классу logging нужны данные, которые
    передаются через terminal для инициализации, поэтому он должен быть
    создаватся перед перед "p.ParseCLI(argc, argv);".
  */
  try {
    p.ParseCLI(argc, argv);

  } catch (const args::Help &) {
    std::cout << p;
    return 0;
  } catch (const args::Error &e) {
    std::cerr << e.what() << std::endl << p;
    return 1;
  }

  log_out *logging = nullptr;
  // Проверка, будем ли записывать логи в файл
  if (args::get(file_name_output).size() > 0) {
    logging = new log_out(args::get(file_name_output));
  } else {
    logging = new log_out();
  }

  try {
    if (version) {
      logging->write_log(log_out::none_info, "BruteSSH version 1.0");
      return 0;
    }
    logging->write_log(log_out::ok, "Start program brutessh!");

    // Проверка на сканирование nuclei
    if ((args::get(scan_ssh) || args::get(scan_honeypot)) &&
        args::get(target).size() > 0) {
      nuclei nucl(args::get(target), logging,
                  args::get(file_name_output).size() > 0 ? true : false);

      if (args::get(scan_ssh)) {
        nucl.detected_ssh();
      }
      if (args::get(scan_honeypot)) {
        nucl.scan_honeypot();
      }
    } else if ((args::get(scan_ssh) || args::get(scan_honeypot)) &&
               args::get(target).size() < 1) {
      throw std::runtime_error("Вы не выбрали цель для сканирования!");
    }

    // Проверки передаваемых значений для порта
    if (args::get(port) < 1 || args::get(port) > 65535) {
      throw std::runtime_error("Выберите значение port-а 1-65535");
    }

    // Инициализируем подключению к файлу
    if (args::get(target).size() > 0 &&
        (args::get(file_name_password).size() > 0 ||
         args::get(file_name_password_user).size() > 0)) {
      // Засекаем время
      auto time_start = std::chrono::high_resolution_clock::now();

      if (args::get(file_name_password).size() > 0 &&
          args::get(file_name_password_user).size() < 1) {
        file_password file(args::get(file_name_password), logging,
                           args::get(threads_max));
        // Запускаем брутфорс
        file.line_read_password(args::get(target), args::get(username),
                                args::get(port));
      } else if (args::get(file_name_password_user).size() > 0 &&
                 args::get(file_name_password).size() < 1) {
        file_password file(args::get(file_name_password_user), logging,
                           args::get(threads_max));
        // Запускаем брутфорс
        file.line_read_password(
            args::get(target),
            args::get(username).size() > 0 ? args::get(username) : "",
            args::get(port), true);
      } else {
        throw std::runtime_error("Должен быть только один флаг для пароля F/f");
      }
      auto time_end = std::chrono::high_resolution_clock::now();
      double time_taken =
          std::chrono::duration_cast<std::chrono::duration<double>>(time_end -
                                                                    time_start)
              .count();
      // Округление времени до одного знака после запятой
      double rounded_time = std::round(time_taken * 10.0) / 10.0;

      std::ostringstream oss;
      oss << std::fixed << std::setprecision(1) << "Программа выполнена за "
          << rounded_time << " секунд";
      logging->write_log(log_out::ok, oss.str());
    }
  } catch (const std::exception &e) {
    logging->write_log(log_out::no_ok, e.what());
  }
  delete logging;

  return 0;
}
